<!--
  -  您可以更改此项目但请不要删除作者署名谢谢，否则根据中华人民共和国版权法进行处理.
  -  You may change this item but please do not remove the author's signature,
  -  otherwise it will be dealt with according to the Copyright Law of the People's Republic of China.
  -
  -  yangbuyi Copyright (c) https://yby6.com 2024.
  -->

<script setup>

import { chatCompletionV1 } from "@/api/audio.js";
import { VueMarkdownIt } from "@f3ve/vue-markdown-it";
import UseChatStore from "@/stores/useChatStore.js";
import UseWebAudioSpeechRecognizer from "@/components/useWebAudioSpeechRecognizer.vue";
import UseCard from "@/components/useCard.vue";

const { proxy } = getCurrentInstance();

const chatStore = UseChatStore();
const durationTime = ref(0)
// 实时音频识别数据
const resultData = ref('');
// 对话按钮
const btnText = ref('按住说话');
// 是否播放
const isPlay = ref(false);
// 音频数据
const audio = ref(null);
const loading = ref(false);
// 在脚本的顶部添加移动距离阈值
const cancelThreshold = ref(20); // 阈值可以根据需要调整
// 记录当前位置
const startY = ref(0)
// 限制是否取消问答
let shouldCancel = ref(false);
// 导航
const open = ref(true)
// 抽屉
const drawer = ref(false)
const direction = ref("rtl")
// 智能体机器人图片
const styleObject = ref({
  'background': 'url() -25px 74px !important',
  'background-size': '100% !important',
  'transform': 'scale(1.0)'
});

// 对接实时音频识别
const resultSpeechRecognizer = (areaDom, countdown) => {
  console.log('resultSpeechRecognizer', areaDom);
  resultData.value = areaDom.replace(/\n/g, '');
}


const onMousedown = (event) => {
  console.log(event.clientY, );
  //判断 event 是否存在 clientY
  if (event?.clientY) {
    startY.value = event.clientY; // 或使用event.touches[0].clientY对于触摸事件
  } else {
    console.log('event 没有 clientY');
    startY.value = event.touches[0].clientY; // 或使用event.touches[0].clientY对于触摸事件
  }



  btnText.value = '松开结束, 下滑取消问答';
  proxy.$refs.useWebAudioSpeechRecognizer.openVoice()
};

const onMouseup = (event) => {

  proxy.$refs.useWebAudioSpeechRecognizer.close()

  if (shouldCancel.value) {
    proxy.$modal.msgSuccess("取消问答")
    setTimeout(() => {
      shouldCancel.value = false;
      btnText.value = '按住说话';
      // 重置按钮按下位置
      startY.value = 0

    }, 20)
    return; // 如果应该取消，则不执行任何操作
  }

  setTimeout(() => {
    //成功也要取消
    shouldCancel.value = false;
    btnText.value = '按住说话';
    // 重置按钮按下位置
    startY.value = 0
    console.log('发送请求: ', resultData.value);
    // 发送请求
    sendRemote()
  }, 200)
};

// 在鼠标移动时
const onMouseMove = (event) => {
  if (startY.value <= 10) {
    return
  }
  const currentY = event.clientY;
  console.log(currentY, startY.value);
  const distance = Math.sqrt(Math.pow(currentY - startY.value, 2));
  console.log(distance, cancelThreshold.value);
  // 超过距离
  if (distance > cancelThreshold.value) {
    btnText.value = '松开取消';
    // 设置 true 限制发送请求
    shouldCancel.value = true;
  } else {
    shouldCancel.value = false;
    btnText.value = '松开结束, 下滑取消问答';
  }
};

const onTouchmove = (event) => {
  event.preventDefault();
  onMouseMove(event.touches[0]);
};

/**
 * 根据索引播放机器人音频
 */
const onPlay = (index) => {

  let item = chatStore.messageList[index];
  console.log(item, isPlay.value);
  item.wink = false;
  audio.value.src = item.stream;
  // 正在播放
  if (isPlay.value) {
    // 则表示暂停
    audio.value.pause();
    isPlay.value = false
  } else {
    audio.value.play();
    isPlay.value = true
  }

  chatStore.messageList[index] = item

  bindAudioEvent(index);
};

/**
 * 计算音频时长
 */
const calculateDuration = (base64String) => {
  if (base64String === '') {
    durationTime.value = 6
    return
  }
  const binaryString = window.atob(base64String);
  const len = binaryString.length;
  const bytes = new Uint8Array(len);
  for (let i = 0; i < len; i++) {
    bytes[i] = binaryString.charCodeAt(i);
  }
  const blob = new Blob([ bytes ], { type: 'audio/mp3' });
  const audio = new Audio();
  audio.src = URL.createObjectURL(blob);
  audio.addEventListener('loadedmetadata', () => {
    durationTime.value = audio.duration;
  });
}

/**
 * 绑定音频事件,变更动画
 */
const bindAudioEvent = (index) => {
  let item = chatStore.messageList[index];

  audio.value.onplaying = () => {
    item.wink = true;
  };

  audio.value.onended = () => {
    item.wink = false;
  };

  // 动画变更
  chatStore.messageList[index] = item
};

/**
 * 发送请求
 */
const sendRemote = async () => {
  console.log('sendRemote', resultData.value);
  if ([ undefined, null, '' ].includes(resultData.value)) {
    console.log("说点话吧");
    return
  }


  // TODO 用户
  chatStore.pushMessage({
    duration: 999,
    stream: undefined,
    isUser: true,
    text: resultData.value,
    sort: chatStore.addIndex(), // 当前消息排序 +1
  })

  // 占位符
  chatStore.pushMessage({
    duration: 0,
    stream: null,
    isUser: false,
    text: "#",
    sort: chatStore.addIndex(), // 当前消息排序 +1
  });

  // 使用的是实时音频识别则直接拿到文本去调用智能体和语音合成
  // loading.value = true
  const messages = chatStore.formatMessages(chatStore.messageList);
  console.log("messages: ", messages);
  chatCompletionV1({
    "audioFile": resultData.value,
    "messages": messages,
    ...chatStore.getAgentData()
  }).then(res => {
    // 机器人音频
    const botVoice = res.data.botVoice
    // 超长一律60s
    if (botVoice === '') {
      durationTime.value = 60
    } else {
      const base64String = botVoice.split(',')[1];
      calculateDuration(base64String)
    }
    // 机器人文本
    const botMessage = res.data.botMessage

    resultData.value = '';

    // 最后一个
    const lastIndex = chatStore.getLastIndex();
    // TODO 机器人
    setTimeout(() => {
      // 替换占位符消息
      chatStore.messageList.splice(lastIndex - 1, 1, {
        duration: Math.round(durationTime.value),
        stream: botVoice,
        isUser: false,
        text: "",
        sort: chatStore.addIndex(), // 当前消息排序 +1
      })


      // 滚动
      scrollToBottom()
      setTimeout(() => {

        // 进行流试(伪)
        chatStore.updateMessage(chatStore.messageList, botMessage, true)

        loading.value = false
        console.log(chatStore.messageList.length - 1, chatStore.messageList[chatStore.getLastIndex() - 1]);
        // 设置未播放
        isPlay.value = false
        // 播放最新的一个语音
        onPlay(chatStore.messageList.length - 1)
        // 清空录音数据
        resultData.value = '';
      }, 50)
    }, 50)


  })
}

const handleOpen = () => {
  drawer.value = true
}

const handleClose = (done) => {
  done();
}

// 使用智能体
const handleUseAgent = (item) => {
  chatStore.saveAgentData(item)
  updateAvatarImage(item.logo)
  drawer.value = false
}


/**
 * 滚动到底部
 */
const scrollToBottom = () => {
  setTimeout(() => {
    // 确保 scrollContainerRef.value 是定义的
    if (proxy.$refs.scrollContainerRef) {
      proxy.$refs.scrollContainerRef.scrollTop = proxy.$refs.scrollContainerRef.scrollHeight + 300;
    }
  }, 10)
};

/**
 * 更新智能体头像
 */
const updateAvatarImage = (newImageUrl) => {
  styleObject.value['background'] = `url('${newImageUrl}') -25px 74px !important`;
}
// chatStore.clearMessage()
const clearMessage = () => {
  proxy.$modal.confirm('是否清空对话信息?').then(() => {
    chatStore.clearMessage()
    drawer.value = false
  })
}

// 监听文本变化
watch(chatStore.messageList, (newValue, oldValue) => {
  scrollToBottom()
});

/**
 * 生命周期
 */
onMounted(() => {

  if (!navigator.mediaDevices) {
    proxy.$modal.notifyError("您的浏览器不支持获取用户设备")
    return;
  }

  if (!window.MediaRecorder) {
    proxy.$modal.notifyError("您的浏览器不支持录音")
    return;
  }

  // 拿到页面上的audio
  audio.value = proxy.$refs.audio;

  scrollToBottom()

  proxy.$nextTick(() => {
    const stream = navigator.mediaDevices.getUserMedia({ audio: true });
    console.log(chatStore.getAgentData());
    const {logo} = chatStore.getAgentData();
    updateAvatarImage(logo)
  })
//   // console.log(chatStore.messageList[chatStore.messageList.length - 1]);
//   chatStore.updateMessage(chatStore.messageList, "" +
//     "好的，已为您找到这张哆啦A梦的画像，希望您会喜欢！" +
//     "![哆啦A梦的画像](https://cdn.yuanqi.tencent.com/hunyuan_open/default/0c03167875a9b4823cf42b135b195b1b.png?sign=1719328114-1719328114-0-033594fe35f39736b7ca26bad4214667616d3ec673fcc72e56403a49e14cfd5e)"
// + "好的，已为您找到这张哆啦A梦的画像，希望您会喜欢！"
// + "好的，已为您找到这张哆啦A梦的画像，希望您会喜欢！"
// + "好的，已为您找到这张哆啦A梦的画像，希望您会喜欢！"
//     +  "![哆啦A梦的画像](https://cdn.yuanqi.tencent.com/hunyuan_open/default/0c03167875a9b4823cf42b135b195b1b.png?sign=1719328114-1719328114-0-033594fe35f39736b7ca26bad4214667616d3ec673fcc72e56403a49e14cfd5e)"
//     + "好的，已为您找到这张哆啦A梦的画像，希望您会喜欢！"
//     + "好的，已为您找到这张哆啦A梦的画像，希望您会喜欢！"
//     , true)

});

</script>

<template>
  <div class="audio-wrapper">
    <div class="phone">

      <div class="phone-body">
        <div class="phone-head">
          <div class="img" id="phone-head-logo" @click="handleOpen"></div>
          <span>童话匠</span>
        </div>
        <div class="phone-content" id="phone-content">
          <div class="msg-list" name="fade" ref="scrollContainerRef">
            <div class="msg">
              <div class="avatar2"></div>
              <div class="audio2">
                欢迎来到玩转新声 - 童话匠 By腾讯云社区领袖杨不易呀
              </div>
            </div>

            <ul v-for="(item, index) in chatStore.messageList" :key="index">
              <!-- 用户 -->
              <li v-if="item.isUser" class="msg">
                <div :class="{avatar:item.isUser}"></div>
                <div v-cloak
                     :class="{
                  audio:item.isUser,
                  audio2:!item.isUser
                }">

                  {{ item.text }}

                </div>
              </li>

              <!-- 机器人 -->
              <li v-else class="msg">
                <div :class="{avatar2:!item.isUser}" :style="styleObject"></div>
                <div v-cloak
                     :class="{
                  audio2: true,
                'duration-item':true,
                  wink: item.wink
                }">
                  <div class="markdown">
                    <vue-markdown-it v-if="item.duration > 0" class="markdown-it" :source="item.text "/>


                    <div class="loader" v-else></div>

                  </div>

                  <div class="voice-content" @click="onPlay(index)" v-if="item.duration > 0"
                       @touchend.prevent="onPlay(index)">
                    <div class="bg voicePlay"></div>
                    <div class="duration2">{{ item.duration }}"</div>
                  </div>

                </div>
              </li>
              <!--              <li style="color: #222222">{{ item.text }}</li>-->
            </ul>
          </div>
        </div>
        <div id="phone-operate" :class="{'phone-operate': true, 'phone-operate-red': shouldCancel}"
             @mousedown="onMousedown"
             @touchstart.prevent="onMousedown"
             @mouseup="onMouseup"
             @touchend.prevent="onMouseup"

             @mousemove="onMouseMove"
             @touchmove.prevent="onTouchmove"
        >
          {{ btnText }}
          <UseWebAudioSpeechRecognizer ref="useWebAudioSpeechRecognizer"
                                       @resultSpeechRecognizer="resultSpeechRecognizer"></UseWebAudioSpeechRecognizer>
        </div>
      </div>

    </div>
    <audio ref="audio"></audio>
  </div>

  <el-drawer
    v-model="drawer"
    title="选择喜欢的智能体进行对话"
    :direction="direction"
    :before-close="handleClose"
    size="100%"
  >
    <div style="width: 100%;">
      <use-card @handleUseAgent="handleUseAgent"></use-card>
      <el-button style="width: 100%;margin-top: 10px;" @click="clearMessage">清空会话</el-button>
    </div>
    <div style="width: 100%;margin-top: 10px;">
      <el-alert title="腾讯云开发者社区领袖杨不易呀" type="success" :closable="false">
        <el-link href="https://cloud.tencent.com/developer/article/2429571" target="_blank">点击: 前往学习TSS/ASR/元器智能体项目</el-link>
      </el-alert>

    </div>
  </el-drawer>

  <el-tour v-model="open">
    <el-tour-step target="#phone-head-logo" title="童话匠Logo"
                  description="这里是Logo,您点击下去将会弹出抽屉供您选择不同智能体的对话氛围!"
    />
    <el-tour-step
      target="#phone-content"
      title="问答区域"
      description="用户与机器人对话"
    />
    <el-tour-step
      target="#phone-operate"
      title="按住说话"
      description="进行语音识别发送问答信息"
    />
  </el-tour>
</template>

<style scoped lang="scss">
.audio-wrapper {
  margin-top: 10px;
  padding: 20px;
  width: 100%;
}


.phone {
  margin: 63px auto;
  padding: 55px 11px 53px;
  width: 221px;
  height: 448px;
  font-size: 12px;
  border-radius: 35px;
  background-image: url("../assets/img/iphone-bg.png");
  box-sizing: border-box;
  user-select: none;
  transform: scale(1.4);
}

.phone-body {
  height: 100%;
  background-color: #fff;
}

.phone-head {
  height: 30px;
  line-height: 30px;
  color: #000;
  background-color: transparent;
  text-align: center;
  position: relative;
}

.phone-head .img {
  width: 20px;
  height: 20px;
  background: url("https://pinia.vuejs.org/logo.svg") 1px 28px;
  background-size: 100%;
  border-radius: 50%;
  position: absolute;
  left: 5px;
  top: 5px;
  animation: shake_logo_img 1s infinite;


  @keyframes shake_logo_img {
    0% {
      transform: rotate(0deg);
    }
    25% {
      transform: rotate(5deg);
    }
    50% {
      transform: rotate(0deg);
    }
    75% {
      transform: rotate(-5deg);
    }
    100% {
      transform: rotate(0deg);
    }
  }

}

.phone-head span {
  display: inline-block;
}

.phone-head span:nth-child(2) {
  //width: 100px;
  text-align: center;
}

.phone-head span:nth-child(3) {
  float: right;
  margin-right: 10px;
}

.phone-content {
  height: 282px;
  background-color: #f1eded;
}

.phone-container {
  line-height: 28px;
  width: 100%;
}

.phone-operate {
  color: #222222;
  position: relative;
  line-height: 28px;
  text-align: center;
  cursor: pointer;
  font-weight: bold;
  box-shadow: 0 -1px 1px rgba(0, 0, 0, .1);
}

.phone-operate:active {
  background-color: rgba(255, 255, 255, 0.65);
}

.phone-operate-red {
  background-color: rgba(215, 53, 53, 0.91) !important;
}

.phone-operate:active:before {
  position: absolute;
  left: 50%;
  transform: translate(-50%, 0);
  top: -2px;
  content: '';
  width: 0%;
  height: 4px;
  background-color: #7bed9f;
  animation: loading 1s ease-in-out infinite backwards;
}

.msg-list {
  margin: 0;
  padding: 0;
  height: 100%;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

.msg-list::-webkit-scrollbar {
  display: none;
}

.msg-list .msg {
  list-style: none;
  padding: 0 8px;
  margin: 10px 0;
  overflow: hidden;
  //cursor: pointer;
}

.msg-list .msg .avatar,
.msg-list .msg .audio,
.msg-list .msg .duration {
  float: right;
}

.msg-list .msg .avatar2,
.msg-list .msg .audio2 {
  float: left;
}


.msg-list .msg .avatar, .msg-list .msg .avatar2 {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  line-height: 24px;
  text-align: center;
  background-color: #000;
  //background: url('https://qiniu.yby6.com/yby-img/image-1701417890342.png?imageView2/0/q/75|watermark/2/text/5p2o5LiN5piT5ZGA/font/5oCd5rqQ6buR5L2T/fontsize/320/fill/IzAwMDAwMA==/dissolve/100/gravity/SouthEast/dx/10/dy/10') 0 0;
  background: url("../assets/img/yby.png") 0 0;
  background-size: 100%;
}

.msg-list .msg .avatar2 {
  //background: url('../assets/img/yq.png') -25px 74px !important;
  background-size: 100% !important;
  transform: scale(1.0);
}

.msg-list .msg .audio, .msg-list .msg .audio2 {
  position: relative;
  margin-right: 6px;
  max-width: 125px;
  min-width: 30px;
  height: 24px;
  line-height: 24px;
  padding: 0 4px 0 10px;
  border-radius: 2px;
  color: #000;
  text-align: left;
  background-color: rgba(107, 197, 107, 0.85);
}

.msg-list .msg .audio2 {
  margin-left: 6px;
  text-align: left;

}


.msg-list .msg.eg {
  cursor: default;
}

.msg-list .msg.eg .audio {
  text-align: left;
}

.msg-list .msg .audio:before {
  position: absolute;
  right: -8px;
  top: 8px;
  content: '';
  display: inline-block;
  width: 0;
  height: 0;
  border-style: solid;
  border-width: 4px;
  border-color: transparent transparent transparent rgba(107, 197, 107, 0.85);
}

.msg-list .msg .audio2:before {
  position: absolute;
  left: -8px;
  top: 8px;
  content: '';
  display: inline-block;
  width: 0;
  height: 0;
  border-style: solid;
  border-width: 4px;
  border-color: transparent rgba(107, 197, 107, 0.85) transparent transparent;
}

.msg-list .msg .audio span, .msg-list .msg .audio2 span {
  color: rgba(255, 255, 255, .8);
  display: inline-block;
  transform-origin: center;
}

.msg-list .msg .audio span:nth-child(1) {
  font-weight: 400;
}

.msg-list .msg .audio span:nth-child(2) {
  transform: scale(0.8);
  font-weight: 500;
}

.msg-list .msg .audio span:nth-child(3) {
  transform: scale(0.5);
  font-weight: 700
}

.msg-list .msg .audio2 span:nth-child(1) {
  transform: scale(0.5);
  font-weight: 300;
}

.msg-list .msg .audio2 span:nth-child(2) {
  transform: scale(0.8);
  font-weight: 400;
}

.msg-list .msg .audio2 span:nth-child(3) {
  font-weight: 500;

}

.msg-list .msg .audio.wink .voicePlay,
.msg-list .msg .audio2.wink .voicePlay {
  animation-name: voicePlay;
  animation-duration: 1s;
  animation-direction: normal;
  animation-iteration-count: infinite;
  animation-timing-function: steps(3);
  top: 0 !important;
}

.duration-item {
  position: relative;
}

//.msg-list .msg .duration, .msg-list .msg .duration2 {
//  //margin: 3px 2px;
//  color: rgba(255, 255, 255, 0.73);
//  position: absolute;
//  right: 18px;
//  top: 0;
//}

.msg-list .msg .duration2 {
  color: rgba(255, 255, 255, 0.73);
  margin-left: 1px;
  font-size: 10px;
}

.fade-enter-active, .fade-leave-active {
  transition: opacity .5s;
}

.fade-enter, .fade-leave-to {
  opacity: 0;
}

//@keyframes wink {
//  from {
//    color: rgba(255, 255, 255, .8);
//  }
//  to {
//    color: rgba(255, 255, 255, .1);
//  }
//}

@keyframes loading {
  from {
    width: 0%;
  }
  to {
    width: 100%;
  }
}


.msg-list .msg .audio, .msg-list .msg .audio2 {
  font-size: 9px !important;
  line-height: 14px !important;
  padding: 5px !important;
  box-sizing: border-box !important;
  height: auto !important;
}


.bg, .bg2 {
  background: url() right 0 no-repeat;
  width: 14px;
  height: 24px;
  background-size: 400%;
  position: relative;
  top: 5px;
}

.bg2 {
  transform: rotateY(200deg);
}

.voice-content {
  display: flex;
  align-items: center;
  cursor: pointer;
}

@keyframes voicePlay {
  0% {
    background-position: 0;
  }
  100% {
    background-position: 100%;
  }
}

.markdown :deep(.markdown-it) {

  img {
    width: 100%;
  }

  a {
    color: #0052d9;
    font-weight: bold;
  }

}


.loader {
  width: 15px;
  height: 15px;
  border: 2px solid #ffffff;
  border-bottom-color: #9cecb0;
  border-radius: 50%;
  box-sizing: border-box;
  animation: rotation-1 1s linear infinite;
}

@keyframes rotation-1 {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}

</style>
